/*
 * Copyright 2011 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "include/core/SkTypes.h"

#if !defined(SK_BUILD_FOR_GOOGLE3)

#include "include/core/SkRect.h"
#include "include/private/base/SkTo.h"
#include "modules/sksg/include/SkSGDraw.h"
#include "modules/sksg/include/SkSGGroup.h"
#include "modules/sksg/include/SkSGInvalidationController.h"
#include "modules/sksg/include/SkSGPaint.h"
#include "modules/sksg/include/SkSGRect.h"
#include "modules/sksg/include/SkSGRenderEffect.h"
#include "modules/sksg/include/SkSGTransform.h"
#include "src/core/SkRectPriv.h"

#include "tests/Test.h"

#include <vector>

static void check_inval(skiatest::Reporter *reporter, const sk_sp<sksg::Node> &root, const SkRect &expected_bounds,
    const SkRect &expected_inval_bounds, const std::vector<SkRect> *expected_damage)
{
    sksg::InvalidationController ic;
    const auto bbox = root->revalidate(&ic, SkMatrix::I());

    if ((false)) {
        SkDebugf("** bbox: [%f %f %f %f], ibbox: [%f %f %f %f]\n", bbox.fLeft, bbox.fTop, bbox.fRight, bbox.fBottom,
            ic.bounds().left(), ic.bounds().top(), ic.bounds().right(), ic.bounds().bottom());
    }

    REPORTER_ASSERT(reporter, bbox == expected_bounds);
    REPORTER_ASSERT(reporter, ic.bounds() == expected_inval_bounds);

    if (expected_damage) {
        const auto damage_count = SkTo<size_t>(ic.end() - ic.begin());
        REPORTER_ASSERT(reporter, expected_damage->size() == damage_count);
        for (size_t i = 0; i < std::min(expected_damage->size(), damage_count); ++i) {
            const auto r1 = (*expected_damage)[i], r2 = ic.begin()[i];
            if ((false)) {
                SkDebugf("*** expected inval: [%f %f %f %f], actual: [%f %f %f %f]\n", r1.left(), r1.top(), r1.right(),
                    r1.bottom(), r2.left(), r2.top(), r2.right(), r2.bottom());
            }
            REPORTER_ASSERT(reporter, r1 == r2);
        }
    }
}

struct HitTest {
    const SkPoint pt;
    sk_sp<sksg::RenderNode> node;
};

static void check_hittest(skiatest::Reporter *reporter, const sk_sp<sksg::RenderNode> &root,
    const std::vector<HitTest> &tests)
{
    for (const auto &tst : tests) {
        const auto *node = root->nodeAt(tst.pt);
        if (node != tst.node.get()) {
            SkDebugf("*** nodeAt(%f, %f) - expected %p, got %p\n", tst.pt.x(), tst.pt.y(), tst.node.get(), node);
        }
        REPORTER_ASSERT(reporter, tst.node.get() == node);
    }
}

static void inval_test1(skiatest::Reporter *reporter)
{
    auto color = sksg::Color::Make(0xff000000);
    auto r1 = sksg::Rect::Make(SkRect::MakeWH(100, 100)), r2 = sksg::Rect::Make(SkRect::MakeWH(100, 100));
    auto grp = sksg::Group::Make();
    auto matrix = sksg::Matrix<SkMatrix>::Make(SkMatrix::I());
    auto root = sksg::TransformEffect::Make(grp, matrix);
    auto d1 = sksg::Draw::Make(r1, color), d2 = sksg::Draw::Make(r2, color);

    grp->addChild(d1);
    grp->addChild(d2);

    {
        // Initial revalidation.
        check_inval(reporter, root, SkRect::MakeWH(100, 100), SkRectPriv::MakeLargeS32(), nullptr);

        check_hittest(reporter, root,
            {
            { { -1, 0 }, nullptr },
            { { 0, -1 }, nullptr },
            { { 100, 0 }, nullptr },
            { { 0, 100 }, nullptr },
            { { 0, 0 }, d2 },
            { { 99, 99 }, d2 },
            });
    }

    {
        // Move r2 to (200 100).
        r2->setL(200);
        r2->setT(100);
        r2->setR(300);
        r2->setB(200);
        std::vector<SkRect> damage = { { 0, 0, 100, 100 }, { 200, 100, 300, 200 } };
        check_inval(reporter, root, SkRect::MakeWH(300, 200), SkRect::MakeWH(300, 200), &damage);

        check_hittest(reporter, root,
            {
            { { -1, 0 }, nullptr },
            { { 0, -1 }, nullptr },
            { { 100, 0 }, nullptr },
            { { 0, 100 }, nullptr },
            { { 0, 0 }, d1 },
            { { 99, 99 }, d1 },

            { { 199, 100 }, nullptr },
            { { 200, 99 }, nullptr },
            { { 300, 100 }, nullptr },
            { { 200, 200 }, nullptr },
            { { 200, 100 }, d2 },
            { { 299, 199 }, d2 },
            });
    }

    {
        // Update the common color.
        color->setColor(0xffff0000);
        std::vector<SkRect> damage = { { 0, 0, 100, 100 }, { 200, 100, 300, 200 } };
        check_inval(reporter, root, SkRect::MakeWH(300, 200), SkRect::MakeWH(300, 200), &damage);
    }

    {
        // Shrink r1.
        r1->setR(50);
        std::vector<SkRect> damage = { { 0, 0, 100, 100 }, { 0, 0, 50, 100 } };
        check_inval(reporter, root, SkRect::MakeWH(300, 200), SkRect::MakeWH(100, 100), &damage);

        check_hittest(reporter, root,
            {
            { { -1, 0 }, nullptr },
            { { 0, -1 }, nullptr },
            { { 50, 0 }, nullptr },
            { { 0, 100 }, nullptr },
            { { 0, 0 }, d1 },
            { { 49, 99 }, d1 },

            { { 199, 100 }, nullptr },
            { { 200, 99 }, nullptr },
            { { 300, 100 }, nullptr },
            { { 200, 200 }, nullptr },
            { { 200, 100 }, d2 },
            { { 299, 199 }, d2 },
            });
    }

    {
        // Update transform.
        matrix->setMatrix(SkMatrix::Scale(2, 2));
        std::vector<SkRect> damage = { { 0, 0, 300, 200 }, { 0, 0, 600, 400 } };
        check_inval(reporter, root, SkRect::MakeWH(600, 400), SkRect::MakeWH(600, 400), &damage);

        check_hittest(reporter, root,
            {
            { { -1, 0 }, nullptr },
            { { 0, -1 }, nullptr },
            { { 25, 0 }, nullptr },
            { { 0, 50 }, nullptr },
            { { 0, 0 }, d1 },
            { { 24, 49 }, d1 },

            { { 99, 50 }, nullptr },
            { { 100, 49 }, nullptr },
            { { 150, 50 }, nullptr },
            { { 100, 100 }, nullptr },
            { { 100, 50 }, d2 },
            { { 149, 99 }, d2 },
            });
    }

    {
        // Shrink r2 under transform.
        r2->setR(250);
        std::vector<SkRect> damage = { { 400, 200, 600, 400 }, { 400, 200, 500, 400 } };
        check_inval(reporter, root, SkRect::MakeWH(500, 400), SkRect::MakeLTRB(400, 200, 600, 400), &damage);

        check_hittest(reporter, root,
            {
            { { -1, 0 }, nullptr },
            { { 0, -1 }, nullptr },
            { { 25, 0 }, nullptr },
            { { 0, 50 }, nullptr },
            { { 0, 0 }, d1 },
            { { 24, 49 }, d1 },

            { { 99, 50 }, nullptr },
            { { 100, 49 }, nullptr },
            { { 125, 50 }, nullptr },
            { { 100, 100 }, nullptr },
            { { 100, 50 }, d2 },
            { { 124, 99 }, d2 },
            });
    }
}

static void inval_test2(skiatest::Reporter *reporter)
{
    auto color = sksg::Color::Make(0xff000000);
    auto rect = sksg::Rect::Make(SkRect::MakeWH(100, 100));
    auto m1 = sksg::Matrix<SkMatrix>::Make(SkMatrix::I()), m2 = sksg::Matrix<SkMatrix>::Make(SkMatrix::I());
    auto t1 = sksg::TransformEffect::Make(sksg::Draw::Make(rect, color), sksg::Transform::MakeConcat(m1, m2)),
         t2 = sksg::TransformEffect::Make(sksg::Draw::Make(rect, color), m1);
    auto root = sksg::Group::Make();
    root->addChild(t1);
    root->addChild(t2);

    {
        // Initial revalidation.
        check_inval(reporter, root, SkRect::MakeWH(100, 100), SkRectPriv::MakeLargeS32(), nullptr);
    }

    {
        // Update the shared color.
        color->setColor(0xffff0000);
        std::vector<SkRect> damage = { { 0, 0, 100, 100 }, { 0, 0, 100, 100 } };
        check_inval(reporter, root, SkRect::MakeWH(100, 100), SkRect::MakeWH(100, 100), &damage);
    }

    {
        // Update m2.
        m2->setMatrix(SkMatrix::Scale(2, 2));
        std::vector<SkRect> damage = { { 0, 0, 100, 100 }, { 0, 0, 200, 200 } };
        check_inval(reporter, root, SkRect::MakeWH(200, 200), SkRect::MakeWH(200, 200), &damage);
    }

    {
        // Update shared m1.
        m1->setMatrix(SkMatrix::Translate(100, 100));
        std::vector<SkRect> damage = { { 0, 0, 200, 200 }, // draw1 prev bounds
            { 100, 100, 300, 300 },                        // draw1 new bounds
            { 0, 0, 100, 100 },                            // draw2 prev bounds
            { 100, 100, 200, 200 } };                      // draw2 new bounds
        check_inval(reporter, root, SkRect::MakeLTRB(100, 100, 300, 300), SkRect::MakeLTRB(0, 0, 300, 300), &damage);
    }

    {
        // Update shared rect.
        rect->setR(50);
        std::vector<SkRect> damage = { { 100, 100, 300, 300 }, // draw1 prev bounds
            { 100, 100, 200, 300 },                            // draw1 new bounds
            { 100, 100, 200, 200 },                            // draw2 prev bounds
            { 100, 100, 150, 200 } };                          // draw2 new bounds
        check_inval(reporter, root, SkRect::MakeLTRB(100, 100, 200, 300), SkRect::MakeLTRB(100, 100, 300, 300),
            &damage);
    }
}

static void inval_test3(skiatest::Reporter *reporter)
{
    auto color1 = sksg::Color::Make(0xff000000), color2 = sksg::Color::Make(0xff000000);
    auto group = sksg::Group::Make();

    group->addChild(sksg::Draw::Make(sksg::Rect::Make(SkRect::MakeWH(100, 100)), color1));
    group->addChild(sksg::Draw::Make(sksg::Rect::Make(SkRect::MakeXYWH(200, 0, 100, 100)), color2));
    auto filter = sksg::DropShadowImageFilter::Make();
    filter->setOffset({ 50, 75 });
    auto root = sksg::ImageFilterEffect::Make(group, filter);

    {
        // Initial revalidation.
        check_inval(reporter, root, SkRect::MakeXYWH(0, 0, 350, 175), SkRectPriv::MakeLargeS32(), nullptr);
    }

    {
        // Shadow-only.
        filter->setMode(sksg::DropShadowImageFilter::Mode::kShadowOnly);
        std::vector<SkRect> damage = { { 0, 0, 350, 175 }, { 50, 75, 350, 175 } };
        check_inval(reporter, root, SkRect::MakeLTRB(50, 75, 350, 175), SkRect::MakeLTRB(0, 0, 350, 175), &damage);
    }

    {
        // Content change -> single/full filter bounds inval.
        color1->setColor(0xffff0000);
        std::vector<SkRect> damage = { { 50, 75, 350, 175 } };
        check_inval(reporter, root, SkRect::MakeLTRB(50, 75, 350, 175), SkRect::MakeLTRB(50, 75, 350, 175), &damage);
    }

    {
        // Visibility change -> full inval.
        group->setVisible(false);
        std::vector<SkRect> damage = { { 50, 75, 350, 175 } };
        check_inval(reporter, root, SkRect::MakeLTRB(50, 75, 350, 175), SkRect::MakeLTRB(50, 75, 350, 175), &damage);
    }
}

static void inval_group_remove(skiatest::Reporter *reporter)
{
    auto draw = sksg::Draw::Make(sksg::Rect::Make(SkRect::MakeWH(100, 100)), sksg::Color::Make(SK_ColorBLACK));
    auto grp = sksg::Group::Make();

    // Readding the child should not trigger asserts.
    grp->addChild(draw);
    grp->removeChild(draw);
    grp->addChild(draw);
}

DEF_TEST(SGInvalidation, reporter)
{
    inval_test1(reporter);
    inval_test2(reporter);
    inval_test3(reporter);
    inval_group_remove(reporter);
}

#endif // !defined(SK_BUILD_FOR_GOOGLE3)
